package ecClient

import (
	"common"
	"common/erpc"
	"common/rpc"
	rs2 "common/rs"
	"context"
	"euplink/center_rpc"
	"euplink/progress"
	"fmt"
	"github.com/zeebo/errs"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"time"
)

type DownloadResult struct {
	Num     int
	Err     error
	Data    []byte
	DevId   string        //设备id
	Dial    string        //连接地址
	UseTime time.Duration //下载时间
}

func (s *DownloadResult) MarshalLogObject(enc zapcore.ObjectEncoder) error {
	enc.AddString("devId", s.DevId)
	enc.AddString("dail", s.Dial)
	enc.AddString("speed", common.NetworkSpeedString(int64(len(s.Data)), s.UseTime))
	enc.AddDuration("useTime", s.UseTime)
	enc.AddInt("total", len(s.Data))
	return nil
}

type client struct {
	index    int
	size     int
	log      *zap.Logger
	rs       *rs2.Rs
	streamId []byte
	dialer   *rpc.TcpDialer
	progress *progress.Progress
}

func NewClient(index int, l *zap.Logger, streamId []byte, size int,
	dialer *rpc.TcpDialer, rs *rs2.Rs, progress *progress.Progress) *client {
	return &client{
		index:    index,
		log:      l,
		dialer:   dialer,
		rs:       rs,
		streamId: streamId,
		progress: progress,
		size:     size,
	}
}

func (s *client) Download(ctx context.Context, nodes []center_rpc.NodeInfo) (results []DownloadResult, err error) {
	var nodeCount = len(nodes)
	var successfulCount, failureCount int
	piecesCtx, piecesCancel := context.WithCancel(ctx)
	defer func() {
		piecesCancel()
		s.log.Info("downloadSegment", zap.String("streamId", fmt.Sprintf("%x", string(s.streamId))),
			zap.Int("segmentIdx", s.index),
			zap.Int("count", nodeCount),
			zap.Int("successfulCount", successfulCount), zap.Int("failureCount", failureCount),
			zap.Error(err))
	}()

	results = make([]DownloadResult, nodeCount)
	ch := make(chan DownloadResult, nodeCount)

	//计算每 piece size
	var pieceSize = int(float64(s.size) * float64(s.rs.Total) / float64(s.rs.Required))
	for i := 0; i < nodeCount; i++ {
		go func(nodeInfo center_rpc.NodeInfo) {
			ch <- s.downloadOneShared(piecesCtx, pieceSize, nodeInfo)
		}(nodes[i])
	}
	//等待结束
	err = errs.New("not enough pieces")
	for i := 0; i < nodeCount; i++ {
		results[i] = <-ch
		if results[i].Err == nil {
			successfulCount++
		} else {
			failureCount++
		}
		if successfulCount >= int(s.rs.Required) {
			err = nil
			piecesCancel()
		}
	}
	return results, err
}

func (s *client) downloadOneShared(ctx context.Context, pieceSize int, nodeInfo center_rpc.NodeInfo) DownloadResult {
	pieceId := fmt.Sprintf("%x-%d-%d", string(s.streamId), s.index, nodeInfo.PieceNum)
	result := DownloadResult{
		Num:     nodeInfo.PieceNum,
		Err:     nil,
		DevId:   nodeInfo.DevId,
		UseTime: 0,
		Data:    make([]byte, 0),
	}
	st := time.Now()
	defer func() {
		result.UseTime = time.Now().Sub(st)
		s.log.Info(fmt.Sprintf("downloadOneShared[%s]", pieceId), zap.Object("result", &result), zap.Error(result.Err))
	}()
	//优先ipv6
	addr := fmt.Sprintf("%s:%d", nodeInfo.Ip, nodeInfo.Port)
	if nodeInfo.Ipv6 != "" {
		addr = fmt.Sprintf("[%s]:%d", nodeInfo.Ipv6, nodeInfo.Port)
	}
	result.Dial = addr

	conn, err := s.dialer.DialAddress(ctx, addr)
	if err != nil {
		result.Err = err
		return result
	}
	_client := erpc.NewDRPCPiecestoreClient(conn)
	defer _client.Close()
	stream, err := _client.Download(ctx, pieceId, 0)
	if err != nil {
		result.Err = err
		return result
	}

	var downloadErr error
	var lastPercent = 0

	for {
		message, recvErr := stream.Recv()
		if recvErr != nil {
			downloadErr = fmt.Errorf("stream.Recv: %s", recvErr)
			break
		}
		result.Data = append(result.Data, message.Data...)

		if ctx.Err() != nil {
			downloadErr = ctx.Err()
			break
		}

		var percent = int(float32(len(result.Data)) * 100 / float32(pieceSize))
		if lastPercent != percent {
			lastPercent = percent
			if s.progress != nil {
				s.progress.UpdatePiecePercent(nodeInfo.PieceNum, int32(lastPercent))
			}
		}
		if message.Done == 1 {
			break
		}
		/*
			//计算实时下载速度，主动放弃太慢的下载
			useTime := time.Now().Sub(t1)
			if useTime < time.Second { //一秒后再计算
				continue
			}
			speed := int(float64(len(data)) / useTime.Seconds())
			//fmt.Println("down speed ", speed)
			down.strategy.Set(nodeInfo.LocalDeviceId, speed)
			if !down.strategy.ShouldContinue(nodeInfo.LocalDeviceId) {
				fmt.Println("abandom slow gay!", nodeInfo.LocalDeviceId)
				downloadErr = errors.New("abandom slow gay")
				break
			}*/
	}
	result.Err = downloadErr
	return result
}
